use num_bigint::BigUint;
use secp256k1::{Message, Secp256k1, SecretKey};

use bc_base::{
    BlockHeight, CompactSignatureBytes, PrivateKeyBytes, PublicKeyBytes, PublicKeyHashBytes,
    Script, TransactionValue,
    constants::{BLOCK_CHIAN_VERSION, SUBSIDY},
    script,
    transaction::{Transaction, TransactionIn, TransactionOut, TransactionOutPoint},
};

pub fn create_transaction(inputs: Vec<TransactionIn>, outputs: Vec<TransactionOut>) -> Transaction {
    let version = BLOCK_CHIAN_VERSION;
    let lock_time = 0;

    Transaction::new(version, inputs, outputs, lock_time)
}

//
// For Regular Transaction
//

pub fn create_transaction_in(
    out_point: &TransactionOutPoint,
    payer_pub_key: &PublicKeyBytes,
    payer_priv_key: &PrivateKeyBytes,
) -> TransactionIn {
    let secp = Secp256k1::new();
    let priv_key =
        SecretKey::from_slice(payer_priv_key.as_bytes_ref()).expect("32 bytes, within curve order");

    // let public_key = PublicKey::from_secret_key(&secp, &priv_key);
    // assert_eq!(public_key.serialize(), *payer_pub_key.as_bytes_ref());
    // let payer_pub_key = PublicKeyBytes::from_slice(&public_key.serialize());

    let message = Message::from_digest(out_point.get_tx_id().as_bytes());
    let payer_sig = secp.sign_ecdsa(&message, &priv_key);
    let payer_sig = payer_sig.serialize_compact();
    let payer_sig = CompactSignatureBytes::new(payer_sig);

    let script_signature = script::create_tx_input_script(payer_sig, payer_pub_key.clone());

    TransactionIn::new(*out_point, script_signature)
}

pub fn create_transaction_out(
    payee_pub_key_hash: &PublicKeyHashBytes,
    value: TransactionValue,
) -> TransactionOut {
    let script_pubkey = script::create_tx_output_script_0(payee_pub_key_hash);
    TransactionOut::new(value, script_pubkey)
}

pub fn get_transaction_sender(script_signature: &Script) -> Option<PublicKeyHashBytes> {
    script::get_transaction_sender(script_signature)
}

pub fn get_transaction_receiver(script_pubkey: &Script) -> Option<PublicKeyHashBytes> {
    script::get_transaction_receiver(script_pubkey)
}

//
// For Coinbase Transaction
//
// https://developer.bitcoin.org/reference/block_chain.html?highlight=block%20height
// As described in BIP34, valid version 2 blocks require a block height parameter in the coinbase.

pub fn create_transaction_in_for_coinbase(nbits: u32, block_height: BlockHeight) -> TransactionIn {
    let out_point = TransactionOutPoint::new_null();
    let script_signature = script::create_tx_input_script_for_coinbase(nbits, block_height);

    TransactionIn::new(out_point, script_signature)
}

//
// For Coinbase Transaction of genesis block
//

pub fn create_coinbase_for_genesis_block() -> Transaction {
    let version = BLOCK_CHIAN_VERSION;
    let inputs = vec![create_tx_in_for_coinbase_of_genesis_block()];
    let outputs = vec![create_tx_out_for_coinbase_of_genesis_block()];
    let lock_time = 1735660800; // 2025-1-1 00:00:00 Asia/Shanghai UTC in Unix timestamp;

    Transaction::new(version, inputs, outputs, lock_time)
}

// create_transaction_in_for_coinbase_of_genesis_block
fn create_tx_in_for_coinbase_of_genesis_block() -> TransactionIn {
    let out_point = TransactionOutPoint::new_null();
    let script_signature = script::create_tx_input_script_for_coinbase_of_genesis_block();

    TransactionIn::new(out_point, script_signature)
}

fn create_tx_out_for_coinbase_of_genesis_block() -> TransactionOut {
    let value = SUBSIDY; // bitcoin value
    let payee_pub_key_hash =
        PublicKeyHashBytes::from_hex_str("9a3dd0eee41587a20d2a25be6a78d405e36a47aa").unwrap();
    create_transaction_out(&payee_pub_key_hash, value)
}

//
// For Coinbase Transaction of mining
//

pub fn create_coinbase_for_mining(
    nbits: u32,
    block_height: BlockHeight,
    extra_nonce: &BigUint,
    miner_pubkey_hash: &PublicKeyHashBytes,
    subsidy: TransactionValue,
) -> Transaction {
    let version = BLOCK_CHIAN_VERSION;
    let inputs = vec![create_tx_in_of_coinbase_for_mining(
        nbits,
        block_height,
        extra_nonce,
    )];
    let outputs = vec![create_tx_out_of_coinbase_for_mining(
        miner_pubkey_hash,
        subsidy,
    )];
    let lock_time = 0;

    Transaction::new(version, inputs, outputs, lock_time)
}

fn create_tx_in_of_coinbase_for_mining(
    nbits: u32,
    block_height: BlockHeight,
    extra_nonce: &BigUint,
) -> TransactionIn {
    let out_point = TransactionOutPoint::new_null();
    let script_signature =
        script::create_tx_input_script_for_coinbase_of_mining(nbits, block_height, extra_nonce);

    TransactionIn::new(out_point, script_signature)
}

fn create_tx_out_of_coinbase_for_mining(
    miner_pubkey_hash: &PublicKeyHashBytes,
    subsidy: TransactionValue,
) -> TransactionOut {
    create_transaction_out(miner_pubkey_hash, subsidy)
}

#[cfg(test)]
mod tests_learning {
    use super::*;

    use secp256k1::PublicKey;

    use bc_base::{PrivateKeyBytes, PublicKeyBytes};

    #[test]
    fn test_private_key_to_public_key() {
        let pub_key = PublicKeyBytes::from_hex_str(
            "03721514a38bbe1cec0fab752beee25c62ac05228a66f230650b23ba30aafbb3e5",
        )
        .unwrap();
        let private_key = PrivateKeyBytes::from_hex_str(
            "3654035c11efff36e9705543529d6509e744b93957e561dc69743add9f825488",
        )
        .unwrap();

        let secp = Secp256k1::new();
        let priv_key = SecretKey::from_slice(private_key.as_bytes_ref())
            .expect("32 bytes, within curve order");
        let public_key = PublicKey::from_secret_key(&secp, &priv_key);

        assert_eq!(public_key.serialize(), *pub_key.as_bytes_ref());
    }
}
